package org.ddevil.data;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map.Entry;

import org.ddevil.data.util.DataUtils;


/**
 * A basic implementation for a {@link DisplayDescriptor}.
 *
 * @author Rich O'Connell
 * @author Eric Lundin
 */
public class BasicDisplayDescriptor implements DisplayDescriptor  {

    private static final String ERR_ARRAY_SIZE_MISMATCH = "In LabelMap constructor. " +
    "ID array length must match label array length.";


    /*
     * Store the DB Id->Label mappings here.
     */
    protected Hashtable<String, String> map;

    /*
     * Store the order here.
     */
    protected ArrayList<String> order;

    /**
     * Default constructor. Creates an empty Display Descriptor.
     */
    public BasicDisplayDescriptor() {
        map = new Hashtable<String, String>();
        order = new ArrayList<String>();

    }

    /**
     * Create an empty Display Descriptor with the specified
     * initial capacity.
     *
     * @param initialCapacity
     */
    public BasicDisplayDescriptor(int initialCapacity) {
        map = new Hashtable<String, String>((int)(initialCapacity/.75), .75f);
        order = new ArrayList<String>(initialCapacity);
    }

    /**
     * Set up the map with the ids and their labels.
     * The two arrays must be equals in size.
     *
     * @param ids
     *          The initial set of ids.
     * @param labels
     *          The labels for the given ids.
     */
    public BasicDisplayDescriptor(String[] ids, String[] labels){
        this(ids, labels, false);
    }

    /**
     * Set up the map with the ids and their labels.
     * If the truncate flag is true then this constructor will
     * truncate the identifier array if it is larger then the label
     * array.<p>
     * i.e.
     *
     * <code><pre>
     * String[] dbids = {"DBID_A", "DBID_B", "DBID_C"};
     * String[] labels = {"A", "B"};
     * new BasicDisplayDescriptor(dbids, labels, true);</pre></code>
     * <p>
     * will result in a label map that looks like:<br>
     * DBID_A -> A<br>
     * DBID_B -> B<br>
     * with no exceptions being thrown.<br>
     * If truncate was false an IllegalArgumentException would have been thrown.<br>
     * <p>
     * If you expect that the arrays are going to be the same size then dont use
     * this constructor as you probaly want the exception to be thrown.
     *
     * @param ids
     *          The initial set of ids.
     * @param labels
     *          The labels for the given ids.
     * @param truncate
     *          If true we will truncate the ids array if it is to large.
     */
    public BasicDisplayDescriptor(String[] ids, String[] labels, boolean truncate){
        this(ids.length);

        if(truncate){
            if(ids.length != labels.length){
            	if (ids.length > labels.length) {
            		ids = DataUtils.truncateArray(ids, labels.length);
            	}
            	else {
            		labels = DataUtils.truncateArray(labels, ids.length);
            	}
            }
        }

        setLabels(ids, labels);
    }

    public BasicDisplayDescriptor(List<String> ids, List<String> labels){
    	this(labels.size());

        for(int i = 0; i < ids.size(); i++){
            setLabel(ids.get(i), labels.get(i));
        }
    }



    /**
     * Map each element in the Id array to the corresponding
     * label in the labels array.  The two arrays must be
     * equal in length.
     *
     * @param ids
     *          The Ids to use.
     * @param labels
     *          The labels for the Ids.
     */
    public void setLabels(String[] ids, String[] labels){
        if(ids != null && labels != null){
            if(ids.length != labels.length){
                throw new IllegalArgumentException(ERR_ARRAY_SIZE_MISMATCH);
            }

            for(int i = 0; i < ids.length; i++){
                setLabel(ids[i], labels[i]);
            }
        }
    }

    /*
     *  (non-Javadoc)
     * @see org.ddevil.data.DisplayDescriptor#setLabel(java.lang.String, java.lang.String)
     */
    public void setLabel(String dbId, String label){
        map.put(dbId, label);
        order.add(dbId);
    }

    /*
     *  (non-Javadoc)
     * @see org.ddevil.data.DisplayDescriptor#getLabel(java.lang.String)
     */
    public String getLabel(String dbId){
        return map.get(dbId);
    }

    /*
     *  (non-Javadoc)
     * @see org.ddevil.data.DisplayDescriptor#getIDForLabel(java.lang.String)
     */
    public String getIDForLabel(String label){
        for(Entry<String,String> e : map.entrySet()){
            if(e.getValue().equals(label)){
                return e.getKey();
            }
        }
        return null;
    }

    /*
     *  (non-Javadoc)
     * @see org.ddevil.data.DisplayDescriptor#getIndexOfId(java.lang.String)
     */
    public int getIndexOfId(String dbId){
        int index = -1;
        for(int i = 0; i < order.size(); i++){
            if(order.get(i).equals(dbId)){
                index = i;
                break;
            }
        }
        return index;
    }

    /*
     *  (non-Javadoc)
     * @see org.ddevil.data.DisplayDescriptor#getDbIdAtIndex(int)
     */
    public String getDbIdAtIndex(int index){
        return order.get(index);
    }

    /**
     * Get all the Ids that this map has a label for.
     * In the the order in which they were added.
     *
     * @return
     *      An array containing all the Ids in this map.
     */
    public String[] getAllIdsArray(){
        String[] ret = new String[order.size()];
        return order.toArray(ret);
    }

    /*
     *  (non-Javadoc)
     * @see org.ddevil.data.DisplayDescriptor#getAllIds()
     */
    @SuppressWarnings("unchecked")
    public ArrayList<String> getAllIds(){
        return (ArrayList<String>)order.clone();
    }

    /*
     *  (non-Javadoc)
     * @see org.ddevil.data.DisplayDescriptor#getAllLabels()
     */
    public ArrayList<String> getAllLabels(){
        ArrayList<String> ret = new ArrayList<String>(size());
        for(String id : order){
            ret.add(map.get(id));
        }
        return ret;
    }

    /*
     *  (non-Javadoc)
     * @see org.ddevil.data.DisplayDescriptor#removeLabelById(java.lang.String)
     */
    public void removeLabelById(String dbId){
        order.remove(dbId);
        map.remove(dbId);
    }

    /**
     * Remove all mappings from this Display Descriptor.
     */
    public void clear() {
        order.clear();
        map.clear();
    }

    /*
     *  (non-Javadoc)
     * @see org.ddevil.data.DisplayDescriptor#containsId(java.lang.String)
     */
    public boolean containsId(String id) {
        return map.containsKey(id);
    }

    /*
     *  (non-Javadoc)
     * @see org.ddevil.data.DisplayDescriptor#size()
     */
    public int size() {
        return order.size();
    }

    /**
     * Check if any mappings are in this Display Descriptor.
     *
     * @return
     *      True if there is at least one mapping.
     */
    public boolean isEmpty() {
        return order.isEmpty();
    }


    @SuppressWarnings("unchecked")
    @Override
    public DisplayDescriptor clone()  {

        BasicDisplayDescriptor ret = new BasicDisplayDescriptor(this.size());
        ret.map = (Hashtable<String,String>)this.map.clone();
        ret.order = (ArrayList<String>)this.order.clone();

        return ret;
    }

    @Override
    public String toString() {
        String[][] arr = new String[this.size()][2];
        for(int i = 0; i < arr.length; i++){
            String id = getDbIdAtIndex(i);
            arr[i][0] = id;
            arr[i][1] = getLabel(id);
        }
        StringBuilder sb = new StringBuilder("BasicDisplayDescriptor:\n");
        sb.append(DataUtils.formatDoubleArrayForPrint(arr));
        return sb.toString();
    }
}
